/* ---------------------------------------------------------------------------- */
/*                  Atmel Microcontroller Software Support                      */
/*                       SAM Software Package License                           */
/* ---------------------------------------------------------------------------- */
/* Copyright (c) 2015, Atmel Corporation                                        */
/*                                                                              */
/* All rights reserved.                                                         */
/*                                                                              */
/* Redistribution and use in source and binary forms, with or without           */
/* modification, are permitted provided that the following condition is met:    */
/*                                                                              */
/* - Redistributions of source code must retain the above copyright notice,     */
/* this list of conditions and the disclaimer below.                            */
/*                                                                              */
/* Atmel's name may not be used to endorse or promote products derived from     */
/* this software without specific prior written permission.                     */
/*                                                                              */
/* DISCLAIMER:  THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR   */
/* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE   */
/* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,      */
/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT */
/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,  */
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF    */
/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING         */
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */
/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                           */
/* ---------------------------------------------------------------------------- */

/*
 * \file
 */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/

#include "chip.h"

#include <assert.h>

/*----------------------------------------------------------------------------
 *        Local definitions
 *----------------------------------------------------------------------------*/

/* Maximum number of interrupt sources that can be defined. This
 * constant can be increased, but the current value is the smallest possible
 * that will be compatible with all existing projects. */
#define MAX_INTERRUPT_SOURCES       7

/*----------------------------------------------------------------------------
 *        Local types
 *----------------------------------------------------------------------------*/

/**
 * Describes a PIO interrupt source, including the PIO instance triggering the
 * interrupt and the associated interrupt handler.
 */
typedef struct _InterruptSource {
	/* Pointer to the source pin instance. */
	const Pin *pPin;

	/* Interrupt handler. */
	void (*handler)(const Pin *);
} InterruptSource;

/*----------------------------------------------------------------------------
 *        Local variables
 *----------------------------------------------------------------------------*/

/* List of interrupt sources. */
static InterruptSource _aIntSources[MAX_INTERRUPT_SOURCES];

/* Number of currently defined interrupt sources. */
static uint32_t _dwNumSources = 0;

/*----------------------------------------------------------------------------
 *        Local Functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Handles all interrupts on the given PIO controller.
 * \param id  PIO controller ID.
 * \param pPio  PIO controller base address.
 */
extern void PioInterruptHandler(uint32_t id, Pio *pPio)
{
	uint32_t status;
	uint32_t i;

	/* Read PIO controller status */
	status = pPio->PIO_ISR;
	status &= pPio->PIO_IMR;

	/* Check pending events */
	if (status != 0) {
		TRACE_DEBUG("PIO interrupt on PIO controller #%d\n\r", id);

		/* Find triggering source */
		i = 0;

		while (status != 0) {
			/* There cannot be an un-configured source enabled. */
			assert(i < _dwNumSources);

			/* Source is configured on the same controller */
			if (_aIntSources[i].pPin->id == id) {
				/* Source has PIOs whose statuses have changed */
				if ((status & _aIntSources[i].pPin->mask) != 0) {
					TRACE_DEBUG("Interrupt source #%d triggered\n\r", i);
					_aIntSources[i].handler(_aIntSources[i].pPin);
					status &= ~(_aIntSources[i].pPin->mask);
				}
			}

			i++;
		}
	}
}

/*----------------------------------------------------------------------------
 *        Global Functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Parallel IO Controller A interrupt handler
 * \Redefined PIOA interrupt handler for NVIC interrupt table.
 */
extern void PIOA_Handler(void)
{
	PioInterruptHandler(ID_PIOA, PIOA);
}

/**
 * \brief Parallel IO Controller B interrupt handler
 * \Redefined PIOB interrupt handler for NVIC interrupt table.
 */
extern void PIOB_Handler(void)
{
	PioInterruptHandler(ID_PIOB, PIOB);
}

/**
 * \brief Parallel IO Controller C interrupt handler
 * \Redefined PIOC interrupt handler for NVIC interrupt table.
 */
extern void PIOC_Handler(void)
{
	PioInterruptHandler(ID_PIOC, PIOC);
}


/**
 * \brief Parallel IO Controller D interrupt handler
 * \Redefined PIOD interrupt handler for NVIC interrupt table.
 */
extern void PIOD_Handler(void)
{
	PioInterruptHandler(ID_PIOD, PIOD);
}

/**
 * \brief Parallel IO Controller E interrupt handler
 * \Redefined PIOE interrupt handler for NVIC interrupt table.
 */
extern void PIOE_Handler(void)
{
	PioInterruptHandler(ID_PIOE, PIOE);
}

/**
 * \brief Initializes the PIO interrupt management logic
 *
 * The desired priority of PIO interrupts must be provided.
 * Calling this function multiple times result in the reset of currently
 * configured interrupts.
 *
 * \param priority  PIO controller interrupts priority.
 */
extern void PIO_InitializeInterrupts(uint32_t dwPriority)
{
	TRACE_DEBUG("PIO_Initialize()\n\r");

	/* Reset sources */
	_dwNumSources = 0;

	/* Configure PIO interrupt sources */
	TRACE_DEBUG("PIO_Initialize: Configuring PIOA\n\r");
	PMC_EnablePeripheral(ID_PIOA);
	PIOA->PIO_ISR;
	PIOA->PIO_IDR = 0xFFFFFFFF;
	NVIC_DisableIRQ(PIOA_IRQn);
	NVIC_ClearPendingIRQ(PIOA_IRQn);
	NVIC_SetPriority(PIOA_IRQn, dwPriority);
	NVIC_EnableIRQ(PIOA_IRQn);

	TRACE_DEBUG("PIO_Initialize: Configuring PIOB\n\r");
	PMC_EnablePeripheral(ID_PIOB);
	PIOB->PIO_ISR;
	PIOB->PIO_IDR = 0xFFFFFFFF;
	NVIC_DisableIRQ(PIOB_IRQn);
	NVIC_ClearPendingIRQ(PIOB_IRQn);
	NVIC_SetPriority(PIOB_IRQn, dwPriority);
	NVIC_EnableIRQ(PIOB_IRQn);

	TRACE_DEBUG("PIO_Initialize: Configuring PIOC\n\r");
	PMC_EnablePeripheral(ID_PIOC);
	PIOC->PIO_ISR;
	PIOC->PIO_IDR = 0xFFFFFFFF;
	NVIC_DisableIRQ(PIOC_IRQn);
	NVIC_ClearPendingIRQ(PIOC_IRQn);
	NVIC_SetPriority(PIOC_IRQn, dwPriority);
	NVIC_EnableIRQ(PIOC_IRQn);

	TRACE_DEBUG("PIO_Initialize: Configuring PIOD\n\r");
	PMC_EnablePeripheral(ID_PIOD);
	PIOD->PIO_ISR;
	PIOD->PIO_IDR = 0xFFFFFFFF;
	NVIC_DisableIRQ(PIOD_IRQn);
	NVIC_ClearPendingIRQ(PIOD_IRQn);
	NVIC_SetPriority(PIOD_IRQn, dwPriority);
	NVIC_EnableIRQ(PIOD_IRQn);

	TRACE_DEBUG("PIO_Initialize: Configuring PIOE\n\r");
	PMC_EnablePeripheral(ID_PIOE);
	PIOE->PIO_ISR;
	PIOE->PIO_IDR = 0xFFFFFFFF;
	NVIC_DisableIRQ(PIOE_IRQn);
	NVIC_ClearPendingIRQ(PIOE_IRQn);
	NVIC_SetPriority(PIOE_IRQn, dwPriority);
	NVIC_EnableIRQ(PIOE_IRQn);
}

/**
 * Configures a PIO or a group of PIO to generate an interrupt on status
 * change. The provided interrupt handler will be called with the triggering
 * pin as its parameter (enabling different pin instances to share the same
 * handler).
 * \param pPin  Pointer to a Pin instance.
 * \param handler  Interrupt handler function pointer.
 */
extern void PIO_ConfigureIt(const Pin *pPin, void (*handler)(const Pin *))
{
	Pio *pio;
	InterruptSource *pSource;

	TRACE_DEBUG("PIO_ConfigureIt()\n\r");

	assert(pPin);
	pio = pPin->pio;
	assert(_dwNumSources < MAX_INTERRUPT_SOURCES);

	/* Define new source */
	TRACE_DEBUG("PIO_ConfigureIt: Defining new source #%d.\n\r", _dwNumSources);

	pSource = &(_aIntSources[_dwNumSources]);
	pSource->pPin = pPin;
	pSource->handler = handler;
	_dwNumSources++;

	/* PIO3 with additional interrupt support
	 * Configure additional interrupt mode registers */
	if (pPin->attribute & PIO_IT_AIME) {
		// enable additional interrupt mode
		pio->PIO_AIMER  = pPin->mask;

		// if bit field of selected pin is 1, set as Rising Edge/High level detection event
		if (pPin->attribute & PIO_IT_RE_OR_HL)
			pio->PIO_REHLSR    = pPin->mask;
		else
			pio->PIO_FELLSR     = pPin->mask;

		/* if bit field of selected pin is 1, set as edge detection source */
		if (pPin->attribute & PIO_IT_EDGE)
			pio->PIO_ESR     = pPin->mask;
		else
			pio->PIO_LSR     = pPin->mask;
	} else {
		/* disable additional interrupt mode */
		pio->PIO_AIMDR       = pPin->mask;
	}
}

/**
 * Enables the given interrupt source if it has been configured. The status
 * register of the corresponding PIO controller is cleared prior to enabling
 * the interrupt.
 * \param pPin  Interrupt source to enable.
 */
extern void PIO_EnableIt(const Pin *pPin)
{
	uint32_t i = 0;
	uint32_t dwFound = 0;

	TRACE_DEBUG("PIO_EnableIt()\n\r");

	assert(pPin != NULL);

#ifndef NOASSERT

	while ((i < _dwNumSources) && !dwFound) {
		if (_aIntSources[i].pPin == pPin)
			dwFound = 1;

		i++;
	}

	assert(dwFound != 0);
#endif

	pPin->pio->PIO_ISR;
	pPin->pio->PIO_IER = pPin->mask;
}

/**
 * Disables a given interrupt source, with no added side effects.
 *
 * \param pPin  Interrupt source to disable.
 */
extern void PIO_DisableIt(const Pin *pPin)
{
	assert(pPin != NULL);

	TRACE_DEBUG("PIO_DisableIt()\n\r");

	pPin->pio->PIO_IDR = pPin->mask;
}

